use std::{cell::OnceCell, collections::BinaryHeap};
use std::collections::{HashMap, HashSet};
use nalgebra::Vector2;
use soukoban::{direction::Direction, Actions, Map, Tiles};
use serde::{Deserialize, Serialize};

use super::state::State;

#[derive(Clone, Copy,PartialEq,Eq,Debug,Default,Serialize,Deserialize)]
pub enum Strategy {
    #[default]
    Fast,
    Mixed,
    OptimalMovePush,
    OptimalPushMove,
}

#[derive(Clone, Copy,PartialEq,Eq,Debug,Default,Serialize,Deserialize)]
pub enum LowerBoundMethod {
    MinimumPush,

    #[default]
    MinimumMove,

    ManhattanDistance,
}


pub struct Solver {
    pub map: Map,
    strategy: Strategy,
    lower_bound_method: LowerBoundMethod,
    lower_bounds: OnceCell<HashMap<Vector2<i32>,usize>>,
    tunnels: OnceCell<HashSet<(Vector2<i32>,Direction)>>,
    visited: HashSet<u64>,
    heap: BinaryHeap<State>
}

#[derive(Debug,PartialEq,Eq,Clone)]
pub enum SolverError {
    Timeout,
    NoSolution,
}

type Result<T> = std::result::Result<T,SolverError>;

impl Solver {
    pub fn new(map: Map,strategy: Strategy,lower_bound_method: LowerBoundMethod) -> Self{
        let mut instance = Self{
            map,
            strategy,
            lower_bound_method,
            lower_bounds: OnceCell::new(),
            tunnels: OnceCell::new(),
            visited: HashSet::new(),
            heap: BinaryHeap::new(),
        };

        instance.heap.push(State::new(
            instance.map.player_position(), 
            instance.map.box_positions().clone(), 
            Actions::new(), 
            &instance,
        ));
        instance
    }


    pub fn strategy(&self) -> Strategy{
        self.strategy
    }


    pub fn lower_bounds(&self) -> &HashMap<Vector2<i32>,usize> {
        self.lower_bounds.get_or_init(|| self.calculate_lower_bounds())
    }

    fn calculate_lower_bounds(&self) -> HashMap<Vector2<i32>,usize>{
        match self.lower_bound_method {
            LowerBoundMethod::MinimumPush => self.minimum_push_lower_bounds(),
            LowerBoundMethod::MinimumMove => self.minimum_move_lower_bounds(),
            LowerBoundMethod::ManhattanDistance => self.manhattan_distance_lower_bounds(),
        }
    }

    fn minimum_push_lower_bounds(&self) -> HashMap<Vector2<i32>,usize> {
        let mut lower_bounds = HashMap::new();
        for goal_position in self.map.goal_positions(){
            lower_bounds.insert(*goal_position,0);
            let mut player_position = None;
            for pull_direction in [
                Direction::Up,
                Direction::Down,
                Direction::Right,
                Direction::Left,
            ] {
                let next_box_position = goal_position + &pull_direction.into();
                let next_player_position = next_box_position + &pull_direction.into();
                if self.map.in_bounds(next_player_position)
                    && !self.map[next_player_position].intersects(Tiles::Wall)
                    && !self.map[next_box_position].intersects(Tiles::Wall)
                {
                    player_position = Some(next_player_position);
                    break;
                }
            }
            if let Some(player_position) = player_position {
                self.minimum_push_to(
                    *goal_position,
                    player_position,
                    &mut lower_bounds,
                    &mut HashSet::new(),
                );
            }else { 
                continue;
            }
        }
        lower_bounds
    }

    fn minimum_move_lower_bounds(&self) -> HashMap<Vector2<i32>,usize> {

    }

    fn manhattan_distance_lower_bounds(&self) -> HashMap<Vector2<i32>,usize> {

    }
}